﻿using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.Goldenlight.Extensions;
using Microsoft.Xna.Framework;

namespace Microsoft.Goldenlight.Framework.Controls
{
    /// <summary>
    /// 	Base class from which all GUI controls must derive. Derives from Container to represent a GUI layout in a hierarchical manner.
    /// </summary>
    public class Control : Container
    {
        #region Fields & Properties

        #endregion

        #region Overrides of LifetimeObject

        #region Implementation of ICloneable

        /// <summary>
        /// 	Called by Clone. Objects need to implement this method to copy all public properties not marked with TorqueCloneIgnore attribute.
        /// </summary>
        /// <param name="newLifetimeObject"> The object to be copied over. </param>
        public override void CopyTo(LifetimeObject newLifetimeObject)
        {
            base.CopyTo(newLifetimeObject);
            var newControl = newLifetimeObject as Control;
            if (newControl != null)
            {
                newControl._visible = _visible;
                newControl._active = _active;
                newControl._focusOnWake = _focusOnWake;
                newControl._minimumSize = _minimumSize;
                newControl._horizontalSizingMode = _horizontalSizingMode;
                newControl._verticalSizingMode = _verticalSizingMode;
                newControl.SetBounds(_bounds);
            }
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// 	Releases all resources used by the Game class.
        /// </summary>
        /// <param name="disposing"> true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
        protected override void Dispose(bool disposing)
        {
            _isDisposed = true;
            if (disposing)
            {
            }
            base.Dispose(disposing);
        }

        #endregion

        #region Register & Unregister

        /// <summary>
        /// 	Inserts processing on a lifetime object after it transitions to the unregistering state due to the invocation of a synchronous unregister operation.
        /// </summary>
        protected override void OnUnregister()
        {
            //TODO: Clear Focus
            base.OnUnregister();
        }

        #endregion

        #endregion

        #region Overrides of Container

        /// <summary>
        /// 	Add a sub-container to current container. Every container which is added belongs in exactly one container.
        /// </summary>
        /// <param name="container"> New sub-container will be added. </param>
        protected override void OnAddContainer(Container container)
        {
            base.OnAddContainer(container);
            var control = container as Control;
            if (control != null)
            {
                if (_awake)
                {
                    control.Awaken();
                }
                OnAddControl(control);
            }
        }

        /// <summary>
        /// 	Remove a sub-container from current container.
        /// </summary>
        /// <param name="container"> Sub-container will be removed. </param>
        protected override void OnRemoveContainer(Container container)
        {
            base.OnRemoveContainer(container);
            var control = container as Control;
            if (control != null)
            {
                if (_awake)
                {
                    control.Sleep();
                }
                OnRemoveControl(control);
            }
        }

        #endregion

        #region Position & Size Operations

        #region Fields & Properties

        protected BoundingRectangle _bounds;

        /// <summary>
        /// 	The position and size of the control.
        /// </summary>
        public BoundingRectangle Bounds
        {
            get
            {
                return _bounds;
            }
            set
            {
                SetBounds(value);
            }
        }

        /// <summary>
        /// 	The position of the control.
        /// </summary>
        [CloneIgnore]
        public Vector2 Position
        {
            get
            {
                return _bounds.Min;
            }
            set
            {
                SetBounds(value, _bounds.Max - _bounds.Min);
            }
        }

        /// <summary>
        /// 	The size of the control.
        /// </summary>
        [CloneIgnore]
        public Vector2 Size
        {
            get
            {
                return _bounds.Max - _bounds.Min;
            }
            set
            {
                SetBounds(_bounds.Min, value);
            }
        }

        private Vector2 _minimumSize = new Vector2(8, 8);

        /// <summary>
        /// 	The minimum size the control is allowed to be when resizing. Any value smaller will clamp the resize to the MinExtent.
        /// </summary>
        public Vector2 MinimumSize
        {
            get
            {
                return _minimumSize;
            }
            set
            {
                _minimumSize = value;
            }
        }

        private HorizontalSizingMode _horizontalSizingMode;

        /// <summary>
        /// 	Defines how this control reacts when a parent control resizes. Defines the horizontal resizing options for this control.
        /// </summary>
        public HorizontalSizingMode HorizontalSizingMode
        {
            get
            {
                return _horizontalSizingMode;
            }
            set
            {
                _horizontalSizingMode = value;
            }
        }

        private VerticalSizingMode _verticalSizingMode;

        /// <summary>
        /// 	Defines how this control reacts when a parent control resizes. Defines the vertical resizing options for this control.
        /// </summary>
        public VerticalSizingMode VerticalSizingMode
        {
            get
            {
                return _verticalSizingMode;
            }
            set
            {
                _verticalSizingMode = value;
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// 	Raised when the bounds of control is updated.
        /// </summary>
        public event EventHandler<PropertyChangeEventArgs<BoundingRectangle>> Resized;

        /// <summary>
        /// 	Raises the Resized event. Override this method to add code to handle when the bounds of control is updated.
        /// </summary>
        /// <param name="oldBounds"> Old bounds argument for the Resized event. </param>
        /// <param name="newBounds"> New bounds argument for the Resized event. </param>
        protected virtual void OnResized(BoundingRectangle oldBounds, BoundingRectangle newBounds)
        {
            var onResized = Resized;
            if (onResized != null)
            {
                onResized(this, new PropertyChangeEventArgs<BoundingRectangle>("Bounds", oldBounds, newBounds));
            }
        }

        #endregion

        #region Handlers

        /// <summary>
        /// 	Called when this object's parent is resized.
        /// </summary>
        /// <param name="parentControl"> The parent control. </param>
        /// <param name="oldSize"> The previous size of the parent control. </param>
        /// <param name="newSize"> The new size of the parent control. </param>
        protected virtual void OnParentResize(Control parentControl, Vector2 oldSize, Vector2 newSize)
        {
            var deltaX = newSize.X - oldSize.X;
            if (Math.Abs(deltaX - 0) > 0.000001)
            {
                switch (_horizontalSizingMode)
                {
                    case HorizontalSizingMode.Width:
                        break;
                    case HorizontalSizingMode.Left:
                        break;
                    case HorizontalSizingMode.Right:
                        break;
                    case HorizontalSizingMode.Center:
                        break;
                    case HorizontalSizingMode.Relative:
                        break;
                }
            }
            var deltaY = newSize.Y - oldSize.Y;
            if (Math.Abs(deltaY - 0) > 0.000001)
            {
                switch (_verticalSizingMode)
                {
                    case VerticalSizingMode.Height:
                        break;
                    case VerticalSizingMode.Top:
                        break;
                    case VerticalSizingMode.Bottom:
                        break;
                    case VerticalSizingMode.Center:
                        break;
                    case VerticalSizingMode.Relative:
                        break;
                }
            }
        }

        /// <summary>
        /// 	Called when a child control of this object is resized.
        /// </summary>
        /// <param name="childControl"> The child control. </param>
        /// <param name="oldSize"> The previous size of the parent control. </param>
        /// <param name="newSize"> The new size of the parent control. </param>
        protected virtual void OnChildResize(Control childControl, Vector2 oldSize, Vector2 newSize)
        {
        }

        #endregion

        #region Set Bounds

        /// <summary>
        /// 	Changes the bounds of this control. A control's bounds is its position and size.
        /// </summary>
        /// <param name="bounds"> The new bounds of this control. </param>
        public void SetBounds(BoundingRectangle bounds)
        {
            SetBounds(bounds.Min, bounds.Max - bounds.Min);
        }

        /// <summary>
        /// 	Changes the size and/or position of this control.
        /// </summary>
        /// <param name="position"> The new location of this control. </param>
        /// <param name="size"> The new size of this control. </param>
        public virtual void SetBounds(Vector2 position, Vector2 size)
        {
            var oldSize = _bounds.Max - _bounds.Min;
            var newSize = new Vector2(MathHelper.Max(_minimumSize.X, size.X), MathHelper.Max(_minimumSize.Y, size.Y));
            var oldBounds = _bounds;
            var newBounds = new BoundingRectangle(position, position + newSize);
            _bounds = newBounds;
            if (oldSize != newSize)
            {
                foreach (var childControl in FindContainers<Control>(false))
                {
                    childControl.OnParentResize(this, oldSize, newSize);
                }
                var parentControl = ParentControl;
                if (parentControl != null)
                {
                    parentControl.OnChildResize(this, oldSize, newSize);
                }
            }
            OnResized(oldBounds, newBounds);
        }

        #endregion

        #endregion

        #region Focus Operations

        #region Fields & Properties

        protected bool _active = true;

        /// <summary>
        /// 	The status of the control. Active controls can respond to input and are focusable.
        /// </summary>
        public bool Active
        {
            get
            {
                return _active;
            }
            set
            {
                _active = value;
                if (!_active)
                {
                    //TODO: Clear Focus
                }
            }
        }

        protected bool _visible = true;

        /// <summary>
        /// 	The visibility of the control, an invisible control does not render.
        /// </summary>
        public bool Visible
        {
            get
            {
                return _visible;
            }
            set
            {
                _visible = value;
                if (!_visible)
                {
                    //TODO: Clear Focus
                }
            }
        }

        /// <summary>
        /// 	Returns true if the control's focus can be changed.
        /// </summary>
        public bool CanFocus
        {
            get
            {
                if (_style == null)
                {
                    return false;
                }
                return _style.Focusable && _visible && _awake && _active;
            }
        }

        private bool _focusOnWake;

        /// <summary>
        /// 	Whether this control should get focus when it awakens.
        /// </summary>
        /// <remarks>
        /// 	Typically only one control in the currently awakening GUI hierarchy should enable this as only one control at any time may have focus. If more than one control enables this functionality, the last control to awaken will receive focus, however each control with this enabled will invoke the GotFocus event.
        /// </remarks>
        public bool FocusOnWake
        {
            get
            {
                return _focusOnWake;
            }
            set
            {
                _focusOnWake = value;
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// 	Raised when the focus of control is gotten.
        /// </summary>
        public event EventHandler<EventArgs<Control>> GotFocus;

        /// <summary>
        /// 	Raises the GotFocus event. Override this method to add code to handle when the focus of control is gotten.
        /// </summary>
        /// <param name="args"> Arguments for the GotFocus event. </param>
        protected internal virtual void OnGotFocus(EventArgs<Control> args)
        {
            var onGotFocus = GotFocus;
            if (onGotFocus != null)
            {
                onGotFocus(this, args);
            }
        }

        /// <summary>
        /// 	Raised when the focus of control is lost.
        /// </summary>
        public event EventHandler<EventArgs<Control>> LostFocus;

        /// <summary>
        /// 	Raises the LostFocus event. Override this method to add code to handle when the focus of control is lost.
        /// </summary>
        /// <param name="args"> Arguments for the LostFocus event. </param>
        protected internal virtual void OnLostFocus(EventArgs<Control> args)
        {
            var onLostFocus = LostFocus;
            if (onLostFocus != null)
            {
                onLostFocus(this, args);
            }
        }

        #endregion

        #endregion

        #region Wake Operations

        #region Fields & Properties

        protected bool _awake;

        /// <summary>
        /// 	An awake control is one that belongs to the currently rendering hierarchy, regardless if it is Visible or Active.
        /// </summary>
        public bool Awake
        {
            get
            {
                return _awake;
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// 	Raised when the status of control is awaken.
        /// </summary>
        public event EventHandler<EventArgs<Control>> Awakened;

        /// <summary>
        /// 	Raises the Awakened event. Override this method to add code to handle when the status of control is awaken.
        /// </summary>
        /// <param name="control"> Control argument for the Awakened event. </param>
        protected virtual void OnAwakened(Control control)
        {
            var onAwakened = Awakened;
            if (onAwakened != null)
            {
                onAwakened(this, new EventArgs<Control>(control));
            }
        }

        /// <summary>
        /// 	Raised when the status of control is asleep.
        /// </summary>
        public event EventHandler<EventArgs<Control>> Sleeped;

        /// <summary>
        /// 	Raises the Sleeped event. Override this method to add code to handle when the status of control is asleep.
        /// </summary>
        /// <param name="control"> Control argument for the Sleeped event. </param>
        protected virtual void OnSleeped(Control control)
        {
            var onSleeped = Sleeped;
            if (onSleeped != null)
            {
                onSleeped(this, new EventArgs<Control>(control));
            }
        }

        #endregion

        #region Awaken & Sleep

        /// <summary>
        /// 	Called when this control is in the process of being awakened. Override this method if your control has requirements that must be met for it to function properly.
        /// </summary>
        protected virtual void OnAwaken()
        {
        }

        /// <summary>
        /// 	Called when this control is in the process of becoming asleep.
        /// </summary>
        protected virtual void OnSleep()
        {
        }

        /// <summary>
        /// 	Called when this control and its children have been added to the Canvas hierarchy.
        /// </summary>
        public bool Awaken()
        {
            if (!_awake)
            {
                if (!FindContainers<Control>(false).Where(control => !control.Awake).All(control => control.Awaken()))
                {
                    return false;
                }
                try
                {
                    OnAwaken();
                    _awake = true;
                    //TODO: Set Focus
                    OnAwakened(this);
                    return true;
                }
                catch (Exception ex)
                {
                    //TODO: Trace
                    Debug.WriteLine(ex.Message);
                    return false;
                }
            }
            return false;
        }

        /// <summary>
        /// 	Called when this control and its children have been removed from the Canvas hierarchy.
        /// </summary>
        public void Sleep()
        {
            if (_awake)
            {
                FindContainers<Control>(false).Where(control => !control.Awake).ForEach(control => control.Sleep());
                try
                {
                    OnSleep();
                    _awake = false;
                    //TODO: Clear Focus
                    OnSleeped(this);
                }
                catch (Exception ex)
                {
                    //TODO: Trace
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        #endregion

        #endregion

        #region Parent Operations

        #region Fields & Properties

        /// <summary>
        /// 	The control which owns this control, in the hierarchy.
        /// </summary>
        /// <remarks>
        /// 	The ParentControl defines the coordinate space for this control and can override the visibility. This control exists within the bounds of the ParentControl.
        /// </remarks>
        public Control ParentControl
        {
            get
            {
                return ParentContainer as Control;
            }
        }

        #endregion

        #region Add & Remove

        /// <summary>
        /// 	Add a child control to current control. Every control which is added belongs in exactly one control.
        /// </summary>
        /// <param name="control"> New child control will be added. </param>
        protected virtual void OnAddControl(Control control)
        {
        }

        /// <summary>
        /// 	Remove a child control from current control.
        /// </summary>
        /// <param name="control"> Child control will be removed. </param>
        protected virtual void OnRemoveControl(Control control)
        {
        }

        #endregion

        /// <summary>
        /// 	Returns true if this control is the parent.
        /// </summary>
        /// <param name="childControl"> Child control will be checked. </param>
        /// <returns> True if this control is the parent. </returns>
        public virtual bool IsParentOf(Control childControl)
        {
            return FindContainers<Control>(false).Any(control => control == childControl || control.IsParentOf(childControl));
        }

        #endregion

        #region Style Operations

        #region Fields & Properties

        private Style _style;

        /// <summary>
        /// 	Each control must have an associated style before it can awaken.
        /// </summary>
        /// <remarks>
        /// 	A style defines a set of properties that define the look or behavior of a control.
        /// </remarks>
        public Style Style
        {
            get
            {
                return _style;
            }
        }

        #endregion

        /// <summary>
        /// 	Called when this control receives a new style, invoked from the set Style property. Override this method if your custom control uses a specialized version of Style.
        /// </summary>
        /// <param name="style"> New style will be set. </param>
        protected virtual void SetStyle(Style style)
        {
            if (style == null)
            {
                throw new ArgumentNullException("style");
            }
            _style = style;
        }

        #endregion
    }
}